home *** CD-ROM | disk | FTP | other *** search
/ The Fatted Calf / The Fatted Calf.iso / Unix / CNews / Source / relay / relaynews.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-03-16  |  15.1 KB  |  577 lines

  1. /*
  2.  * relaynews - relay Usenet news (version C)
  3.  * See the file COPYRIGHT for the copyright notice.
  4.  *
  5.  * relaynews should be setuid-news and setgid-news on systems that lack a
  6.  * mkdir system call, thus requiring the invocation of a setuid-root mkdir
  7.  * command.  You'll need to install setnewsids setuid-root if
  8.  * setuid(geteuid()) doesn't work on such systems (e.g. on V7 and possibly
  9.  * System III).
  10.  *
  11.  * Written by Geoff Collyer, 15-20 November 1985 and revised periodically
  12.  * since.
  13.  *
  14.  * relaynews parses article headers, rejects articles by newsgroup &
  15.  * message-id, files articles, updates the active & history files,
  16.  * transmits articles, and honours (infrequent) control messages, which do
  17.  * all sorts of varied and rococo things.  Control messages are implemented
  18.  * by separate programs.  relaynews reads a "sys" file to control the
  19.  * transmission of articles but can function as a promiscuous leaf node
  20.  * without one.  See Internet RFC 1036 for the whole story and RFC 850
  21.  * for background.
  22.  *
  23.  * A truly radical notion: people may over-ride via environment variables
  24.  * the compiled-in default directories so IHCC kludges are not needed and
  25.  * testing is possible (and encouraged) in alternate directories.  This
  26.  * does cause a loss of privilege, to avoid spoofing.
  27.  *
  28.  * The disused old unbatched ihave/sendme protocol is gone because it was
  29.  * too wasteful; use the batched form instead (see the ihave sys flag
  30.  * ("I") instead).
  31.  */
  32.  
  33. #include <stdio.h>
  34. #include <stdlib.h>
  35. #include <ctype.h>
  36. #include <signal.h>        /* to make locking safe */
  37. #include <errno.h>
  38. #include "fixerrno.h"
  39. #include <sys/types.h>
  40. #include <sys/stat.h>
  41.  
  42. #include "libc.h"
  43. #include "news.h"
  44. #include "config.h"
  45. #include "fgetmfs.h"
  46. #include "active.h"
  47. #include "caches.h"
  48. #include "fileart.h"
  49. #include "headers.h"
  50. #include "history.h"
  51. #include "transmit.h"
  52.  
  53. /*
  54.  * setuid-root program to set ids to news/news & re-exec rnews with
  55.  * NEWSPERMS in the environment to break loops.
  56.  */
  57. #ifndef SETNEWSIDS
  58. #define SETNEWSIDS "setnewsids"
  59. #endif
  60.  
  61. /* exports */
  62. char *progname = "relaynews";
  63. boolean okrefusal = YES;            /* okay to refuse articles? */
  64. char *exclude = NULL;                /* site to exclude, for erik */
  65. boolean histreject = NO;            /* keep history of rejects? */
  66. long staledays = 0;            /* articles stale after this many days */
  67. boolean genxref = NO;            /* iff true, always generate Xref: */
  68. boolean blvxref = NO;            /* iff true, believe Xref: contents */
  69. char *blvsite = NULL;            /* ... for this site only. */
  70. boolean dupsokay = NO;            /* iff true, allow duplicates ... */
  71. char *dupsite = NULL;            /* ... from this site only. */
  72.  
  73. /* internal */
  74. static boolean userealids = NO;
  75. static boolean uunlink = NO;
  76. static char incsfx[] = "in.coming";    /* need this for -u, alas */
  77.  
  78. /* imports */
  79. extern int optind;            /* set by getopt */
  80. extern char *optarg;
  81. extern statust cpinsart();        /* from procart.c */
  82.  
  83. /* forwards */
  84. extern void prelude(), setids(), procopts(), redirectlogs(), logfile();
  85. extern void getwdandcd();
  86. extern statust procargs(), relnmprocess(), process(), unbatch();
  87. extern boolean batchln();
  88. FORWARD boolean debugon();
  89. FORWARD long maxlong();
  90.  
  91. /*
  92.  * main - take setuid precautions, switch to "news" ids, ignore signals,
  93.  * handle options, lock news system, process files & unlock news system.
  94.  */
  95. int
  96. main(argc, argv)
  97. int argc;
  98. char *argv[];
  99. {
  100.     statust status = ST_OKAY;
  101.     int redirlogs = 0;    /* redirect n std output streams to logs */
  102.     char *origdir = NULL;        /* current directory at start */
  103.  
  104.     if (argc > 0)
  105.         progname = argv[0];
  106. #ifdef CSRIMALLOC
  107.     mal_debug(0);    /* was 2; 3 is too slow */
  108.     mal_leaktrace(0);    /* was 1 */
  109. #endif
  110.     prelude(argv);        /* various precautions; switch to "news" */
  111.  
  112.     /* ignore signals (for locking). relaynews runs quickly, so don't worry. */
  113.     (void) signal(SIGINT, SIG_IGN);
  114.     (void) signal(SIGQUIT, SIG_IGN);
  115.     (void) signal(SIGHUP, SIG_IGN);
  116.     (void) signal(SIGTERM, SIG_IGN);
  117.     (void) signal(SIGPIPE, SIG_IGN);    /* we check write returns */
  118.  
  119.     procopts(argc, argv, &origdir, &redirlogs, &okrefusal);
  120.  
  121.     (void) morefds();        /* ask Unix for more descriptors */
  122.     newslock();            /* done here due to dbm internal cacheing */
  123.     if (redirlogs > 0) {
  124.         redirectlogs(redirlogs); /* redirect std output streams to logs */
  125. #ifdef MANYERRORS
  126.         (void) putc('\n', stderr);    /* leave a blank line */
  127.         /* prints "Jun  5 12:34:56" */
  128.         timestamp(stderr, (time_t *)NULL);
  129.         (void) putc('\n', stderr);
  130. #endif
  131.     }
  132.  
  133.     getwdandcd(argc, argv, &origdir);
  134.     status |= procargs(argc, argv, &origdir);
  135.  
  136.     status |= synccaches();        /* being cautious: write & close caches */
  137.     status |= closehist();
  138.     (void) fflush(stdout);        /* log file */
  139.     (void) fflush(stderr);        /* errlog file */
  140.  
  141. #ifdef notdef
  142. #ifdef CSRIMALLOC
  143.     mal_dumpleaktrace(fileno(stderr));
  144. #endif
  145. #endif
  146.     newsunlock();
  147.     exit(status);
  148.     /* NOTREACHED */
  149. }
  150.  
  151. /*
  152.  * reset various environmental things for safety: umask, alarm,
  153.  * environment variables (PATH, IFS), standard file descriptors,
  154.  * user & group ids.
  155.  */
  156. void
  157. prelude(argv)                /* setuid daemon prelude */
  158. char **argv;
  159. {
  160.     register char *newpath;
  161.  
  162.     (void) umask(newsumask());
  163.     (void) alarm(0);        /* cancel any pending alarm */
  164.     /* TODO: suppress chatter, lock freeing on failure here */
  165.     newpath = str3save("PATH=", newspath(), "");
  166.     if (newpath == NULL)
  167.         exit(1);        /* no chatter until stdfdopen */
  168.     if (putenv(newpath) ||
  169.         putenv("SHELL=/bin/sh") ||
  170.         putenv("IFS= \t\n"))
  171.         exit(1);        /* no chatter until stdfdopen */
  172.     closeall(1);            /* closes all but std descriptors */
  173.     stdfdopen();            /* ensure open standard descriptors */
  174.     setids(argv);            /* change of real and effective ids */
  175. }
  176.  
  177. /*
  178.  * change real and effective ids to real ids if unprivileged() is called,
  179.  * else to effective ("news") ids.  ctlfile((char *)0) will trigger a call
  180.  * to unprivileged() if any environment variables override the default
  181.  * path names.  unprivileged() in turn sets userealids.
  182.  *
  183.  * If setuid(geteuid()) fails, try execing a small, setuid-root program
  184.  * to just do "getpwnam(), getgrnam() (with NEWSPERMS set), setgid(),
  185.  * setuid()," and exec this program again.  If NEWSPERMS is set,
  186.  * the failure is a fatal error (recursive loop).
  187.  * This program (relaynews) can be setuid-news.
  188.  *
  189.  * The peculiar tests for failure (getuid() != newsuid) are to work
  190.  * around a Xenix bug which returns 0 from setuid() upon failure.
  191.  */
  192. void
  193. setids(argv)
  194. char **argv;
  195. {
  196.     int newsuid, newsgid;
  197.  
  198.     (void) ctlfile((char *)NULL);
  199.     if (userealids)
  200.         newsuid = getuid(), newsgid = getgid();
  201.     else
  202.         newsuid = geteuid(), newsgid = getegid();
  203.     if (setgid(newsgid) < 0 || setuid(newsuid) < 0 ||
  204.         getgid() != newsgid || getuid() != newsuid) {
  205.         if (getenv("NEWSPERMS") != 0)
  206.             error("recursive loop setting ids", "");
  207.         /*
  208.          * normally we would use execvp, but for security SETNEWSIDS
  209.          * had better be an absolute path to a binary.
  210.          */
  211.         /* TODO: ctlfile -> binfile */
  212.         execv(ctlfile(SETNEWSIDS), argv);
  213.         error("can't exec `%s' to set ids", ctlfile(SETNEWSIDS));
  214.         /* NOTREACHED */
  215.     }
  216.     /* we are now running as news, so you can all relax */
  217. }
  218.  
  219. /*
  220.  * parse options and set flags
  221.  */
  222. void
  223. procopts(argc, argv, origdirp, redirlogsp, okrefusalp)
  224. int argc;
  225. char **argv;
  226. char **origdirp;
  227. int *redirlogsp;
  228. boolean *okrefusalp;
  229. {
  230.     int c, errflg = 0;
  231.     char *incdir;
  232.  
  233.     while ((c = getopt(argc, argv, "a:b:d:gino:rsx:u")) != EOF)
  234.         switch (c) {
  235.         case 'a':
  236.             dupsokay = YES;
  237.             dupsite = optarg;
  238.             break;
  239.         case 'b':
  240.             blvxref = YES;
  241.             blvsite = optarg;
  242.             break;
  243.         case 'd':        /* -d debug-options; thanks, henry */
  244.             if (!debugon(optarg))
  245.                 errflg++;    /* debugon has complained */
  246.             break;
  247.         case 'g':
  248.             genxref = YES;
  249.             break;
  250.         case 'i':        /* redirect stdout to log (inews) */
  251.             *redirlogsp = 1; /* just stdout */
  252.             break;
  253.         case 'n':        /* nntp mode: keep history of rejects */
  254.             histreject = YES;
  255.             break;
  256.         case 'o':
  257.             /* "oldness": drop articles older than this many days */
  258.             staledays = atol(optarg);
  259.             break;
  260.         case 'r':    /* redirect std. ostreams to logs (rnews) */
  261.             *redirlogsp = 2; /* stdout & stderr */
  262.             break;
  263.         case 's':        /* dropping input is serious (inews) */
  264.             *okrefusalp = NO;
  265.             break;
  266.         case 'u':        /* unlink good batches when done */
  267.             incdir = fullartfile(incsfx);
  268.             if (cwdis(incdir)) {
  269.                 uunlink = YES;
  270.                 *origdirp = strsave(incdir);
  271.             } else
  272.                 (void) fprintf(stderr,
  273.             "%s: -u ignored - current directory is not %s\n",
  274.                     progname, incdir);
  275.             break;
  276.         case 'x':        /* -x site: don't send to site */
  277.             /* you're welcome, erik */
  278.             /* erik says he only needs one -x per inews */
  279.             if (exclude != NULL) {
  280.                 (void) fprintf(stderr,
  281.                     "%s: more than one -x site (%s)\n",
  282.                     progname, optarg);
  283.                 errflg++;
  284.             } else
  285.                 exclude = optarg;
  286.             break;
  287.         default:
  288.             errflg++;
  289.             break;
  290.         }
  291.     if (errflg) {
  292.         (void) fprintf(stderr,
  293. "usage: %s [-ginrsu][-d fhlmt][-x site][-o days][-b xrefsite][-a dupsite]\n",
  294.             progname);
  295.         exit(1);
  296.     }
  297. }
  298.  
  299. /*
  300.  * called if NEWSARTS, NEWSCTL, NEWSBIN, etc. are non-standard.
  301.  * this may be due to legitimate testing, but we can't tell.
  302.  * the error message will at least be seen by a human trying to
  303.  * track down a problem, even if stderr isn't normally seen.
  304.  */
  305. void
  306. unprivileged(reason)
  307. char *reason;
  308. {
  309.     userealids = YES;
  310.     (void) fprintf(stderr,
  311.     "%s: warning: renouncing setuid due to nonstandard `%s' in environment\n",
  312.                             progname, reason);
  313. }
  314.  
  315. STATIC boolean
  316. debugon(dbopt)
  317. register char *dbopt;
  318. {
  319.     statust status = YES;
  320.  
  321.     for (; *dbopt != '\0'; dbopt++)
  322.         switch (*dbopt) {
  323.         case 'f':
  324.             filedebug(YES);
  325.             break;
  326.         case 'h':
  327.             hdrdebug(YES);
  328.             break;
  329.         case 'l':
  330.             lockdebug(YES);
  331.             break;
  332.         case 'm':
  333.             matchdebug(YES);
  334.             break;
  335.         case 't':
  336.             transdebug(YES);
  337.             break;
  338.         default:
  339.             status = NO;    /* unknown debugging option */
  340.             (void) fprintf(stderr, "%s: bad -d %c\n",
  341.                 progname, *dbopt);
  342.             break;
  343.         }
  344.     return status;
  345. }
  346.  
  347. /*
  348.  * Redirect stdout or stderr into log files at known locations.
  349.  */
  350. void
  351. redirectlogs(count)
  352. int count;
  353. {
  354.     if (count > 0)
  355.         logfile(stdout, ctlfile("log"));
  356.     if (count > 1)
  357.         logfile(stderr, ctlfile("errlog"));
  358. }
  359.  
  360. void
  361. logfile(stream, name)            /* redirect stream into name */
  362. FILE *stream;
  363. char *name;
  364. {
  365.     if (freopen(name, "a", stream) == NULL)
  366.         errunlock("can't redirect standard stream to `%s'", name);
  367. }
  368.  
  369. /*
  370.  * if argv contains relative file name arguments, save current directory name
  371.  * in malloced memory, through origdirp.
  372.  * then change directory to the spool directory ($NEWSARTS).
  373.  */
  374. void
  375. getwdandcd(argc, argv, origdirp)
  376. int argc;
  377. char **argv;
  378. char **origdirp;
  379. {
  380.     register int argind;
  381.     boolean needpwd = NO;
  382.     char dirtmp[MAXPATH];            /* much bigger than needed */
  383.  
  384.     if (*origdirp == NULL) {            /* not yet set? */
  385.         for (argind = optind; argind < argc; argind++)
  386.             if (argv[argind][0] != FNDELIM)
  387.                 needpwd = YES;
  388.  
  389.         *origdirp = "/???";        /* pessimism */
  390.         if (needpwd && getcwd(dirtmp, sizeof dirtmp) != 0)
  391.             *origdirp = dirtmp;
  392.         *origdirp = strsave(*origdirp);    /* save a smaller copy */
  393.     }
  394.     cd(fullartfile((char *)NULL));        /* move to spool directory */
  395. }
  396.  
  397. int                        /* actually boolean */
  398. cwdis(dir)
  399. char *dir;
  400. {
  401.     struct stat dotstat, dirstat;
  402.     static char dot[] = ".";        /* name of current dir. */
  403.  
  404.     if (stat(dir, &dirstat) < 0) {
  405.         warning("no %s directory", dir);
  406.         return NO;
  407.     }
  408.     if (stat(dot, &dotstat) < 0) {
  409.         warning("no %s directory", dot);
  410.         return NO;
  411.     }
  412.     return dotstat.st_dev == dirstat.st_dev &&
  413.            dotstat.st_ino == dirstat.st_ino;
  414. }
  415.  
  416. /*
  417.  * process files named as arguments (or implied)
  418.  */
  419. statust
  420. procargs(argc, argv, origdirp)
  421. int argc;
  422. char **argv;
  423. char **origdirp;
  424. {
  425.     register statust status = ST_OKAY;
  426.  
  427.     if (optind == argc)
  428.         status |= process(stdin, "(stdin)");
  429.     else
  430.         for (; optind < argc; optind++)
  431.             status |= relnmprocess(argv[optind], *origdirp);
  432.     nnfree(origdirp);
  433.     return status;
  434. }
  435.  
  436. statust
  437. relnmprocess(name, origdir)        /* process a (relative) file name */
  438. char *name, *origdir;
  439. {
  440.     register statust status = ST_OKAY;
  441.     register FILE *in;
  442.     register char *fullname = (name[0] != FNDELIM?
  443.         str3save(origdir, SFNDELIM, name): strsave(name));
  444.  
  445.     in = fopenwclex(fullname, "r");
  446.     if (in != NULL) {
  447.         status |= process(in, fullname);
  448.         (void) nfclose(in);
  449.         /*
  450.          * in unlink mode, try to unlink good batches in a
  451.          * known-safe place, but not very hard.
  452.          * main() ensured that relative names are relative to
  453.          * $NEWSARTS/in.coming.
  454.          */
  455.         if (uunlink && !(status&(ST_DROPPED|ST_SHORT)) &&
  456.             strchr(name, FNDELIM) == NULL) {
  457.             char *incname = str3save(incsfx, SFNDELIM, name);
  458.  
  459.             (void) unlink(incname);
  460.             free(incname);
  461.         }
  462.     }
  463.     free(fullname);
  464.     return status;
  465. }
  466.  
  467. /*
  468.  * process - process input file
  469.  * If it starts with '#', assume it's a batch and unravel it,
  470.  * else it's a single article, so just inject it.
  471.  */
  472. statust
  473. process(in, inname)
  474. FILE *in;
  475. char *inname;
  476. {
  477.     register int c;
  478.  
  479.     if ((c = getc(in)) == EOF)
  480.         return ST_OKAY;         /* normal EOF */
  481.     (void) ungetc(c, in);
  482.     if (c == '#')
  483.         return unbatch(in, inname);
  484.     else
  485.         /* -SIZENUL is to avoid overflow later during +SIZENUL */
  486.         return cpinsart(in, inname, maxlong() - SIZENUL, NO);
  487. }
  488.  
  489. /*
  490.  * compute the largest number that can be stored in a long.  in theory, 
  491.  * #define MAXLONG ((long)(~(unsigned long)0 >> 1))
  492.  * will do the job, but old compilers don't have "unsigned long", don't
  493.  * like casts in initialisers, or otherwise miscompute.
  494.  */
  495. STATIC long
  496. maxlong()
  497. {
  498.     register int bits = 0;
  499.     register unsigned word = 1;        /* "unsigned" avoids overflow */
  500.     static long savemaxlong = 0;
  501.  
  502.     if (savemaxlong > 0)
  503.         return savemaxlong;
  504.     for (bits = 0, word = 1; word != 0; word <<= 1)
  505.         bits++;
  506.     /* bits/sizeof word = bits per char; all bits on but the sign bit */
  507.     savemaxlong = ~(1L << (bits/sizeof word * sizeof savemaxlong - 1));
  508.     if (savemaxlong <= 0) {            /* sanity check */
  509.         errno = 0;
  510.         errunlock("maxlong is non-positive; your compiler is broken", "");
  511.     }
  512.     return savemaxlong;
  513. }
  514.  
  515. /*
  516.  * Unwind "in" and insert each article.
  517.  * For each article, call cpinsart to copy the article from "in" into
  518.  * a (temporary) file in the news spool directory and rename the temp file
  519.  * to the correct final name if it isn't right already.
  520.  *
  521.  * If the unbatcher gets out of sync with the input batch, the unbatcher
  522.  * will print and discard each input line until it gets back in sync.
  523.  */
  524. statust
  525. unbatch(in, inname)
  526. register FILE *in;
  527. char *inname;
  528. {
  529.     register int c;
  530.     /* register */ char *line;
  531.     register statust status = ST_OKAY;
  532.     long charcnt;
  533.  
  534.     while (!(status&ST_NEEDATTN) && (c = getc(in)) != EOF) {
  535.         (void) ungetc(c, in);
  536.         while ((line = fgetms(in)) != NULL &&
  537.             !batchln(line, &charcnt)) {        /* returns charcnt */
  538.             status |= ST_DROPPED;
  539.             (void) fprintf(stderr,
  540.                 "%s: unbatcher out of synch, tossing: ",
  541.                 progname);
  542.                 (void) fputs(line, stderr);
  543.             free(line);
  544.         }
  545.         nnfree(&line);            /* free "#! rnews n" */
  546.         if (!feof(in) && charcnt > 0)    /* anything to do? */
  547.             status |= cpinsart(in, inname, charcnt, YES);
  548.     }
  549.     if (ferror(in))
  550.         errunlock("error reading `%s'", inname);
  551.     return status;
  552. }
  553.  
  554. /*
  555.  * Is line a batcher-produced line (#! rnews count)?
  556.  * If so, return the count through charcntp.
  557.  * This is slightly less convenient than sscanf, but a lot smaller.
  558.  */
  559. boolean
  560. batchln(line, charcntp)
  561. register char *line;
  562. register long *charcntp;
  563. {
  564.     register char *countp;
  565.     static char batchtext[] = "#! rnews ";
  566.  
  567.     countp = line + STRLEN(batchtext);
  568.     if (STREQN(line, batchtext, STRLEN(batchtext)) &&
  569.         isascii(*countp) && isdigit(*countp)) {
  570.         *charcntp = atol(countp);
  571.         return YES;
  572.     } else {
  573.         *charcntp = 0;
  574.         return NO;
  575.     }
  576. }
  577.